package org.example;

import com.github.benmanes.caffeine.cache.*;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

public class CaffeineTest2 {

    private static final Map<String, String> MAP = new HashMap<>();


    static {
        MAP.put("test1", "value1");
        MAP.put("test2", "value2");
        MAP.put("test3", "value3");
        MAP.put("test4", "value4");
    }


    @DisplayName("测试LoadingCache")
    @Test
    public void testLoadingCache() {
        LoadingCache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, String>() {
                    @Override
                    public @Nullable String load(@NonNull String key) throws Exception {
                        //默认的数据加载实现，当调用get取值的时候，如果key没有对应的值，就调用这个方法进行加载
                        System.out.println("load data --- " + key);
                        //模拟从数据库中获取数据
                        return MAP.get(key);
                    }
                });
        System.out.println(cache.get("test1")); //第一次的时候会调用load方法
        System.out.println(cache.get("test1")); //第二次不会调用load方法
    }


    @Test
    public void testAsyncCache() throws ExecutionException, InterruptedException {
        AsyncCache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(10_000)
                .buildAsync();

        // 查找缓存元素，如果不存在，则异步生成
        CompletableFuture<String> value = cache.get("test1", k -> {
            //异步加载
            System.out.println(Thread.currentThread().getName()); // ForkJoinPool.commonPool-worker-3
            System.out.println("load cache ---" + k);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return MAP.get(k);
        });
        System.out.println(Thread.currentThread().getName()); //main
        System.out.println("=========");
        System.out.println(value.get()); //value1, 阻塞
    }

    @Test
    public void testAsynchronouslyLoadingCache() throws ExecutionException, InterruptedException {
        AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(10_000)
                //异步的封装一段同步操作来生成缓存元素
                .buildAsync(new CacheLoader<String, String>() {
                    @Override
                    public @Nullable String load(@NonNull String key) throws Exception {
                        System.out.println(Thread.currentThread().getName()); // ForkJoinPool.commonPool-worker-3
                        System.out.println("load cache ---" + key);
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return MAP.get(key);
                    }
                });

        CompletableFuture<String> value = cache.get("test1");
        System.out.println(Thread.currentThread().getName()); //main
        System.out.println("=========");
        System.out.println(value.get()); //value1 阻塞
    }


    @Test
    public void testAsynchronouslyLoadingCache2() throws ExecutionException, InterruptedException {
        AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(10_000)
                //构建一个异步缓存元素操作并返回一个future
                .buildAsync(new AsyncCacheLoader<String, String>() {
                    @Override
                    public @NonNull CompletableFuture<String> asyncLoad(@NonNull String key, @NonNull Executor executor) {
                        System.out.println(Thread.currentThread().getName()); //main
                        return CompletableFuture.supplyAsync(() -> {
                            System.out.println("load cache");
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()); // ForkJoinPool.commonPool-worker-3
                            return MAP.get(key);
                        });
                    }

                });

        System.out.println(cache.get("test1").get()); // value1 阻塞
    }


    /**
     * Caffeine.weakValues()在保存value的时候将会使用弱引用。
     * 这允许在GC的过程中，当value没有被任何强引用指向的时候去将缓存元素回收。
     * 由于GC只依赖于引用相等性。这导致在这个情况下，缓存将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。
     */
    @Test
    public void testWeakValues() {
        Cache<String, String> cache = Caffeine.newBuilder()
                .weakKeys()
                .weakValues()
                .build();
        cache.put("test1", new String("value"));
        System.out.println(cache.asMap());//{test1=value}
        System.out.println(cache.getIfPresent("test1")); //value
        System.gc();
        System.out.println(cache.getIfPresent("test1")); //null
        System.out.println(cache.asMap());//{}
    }

    /**
     * Caffeine.weakKeys() 在保存key的时候将会进行弱引用。
     * 这允许在GC的过程中，当key没有被任何强引用指向的时候去将缓存元素回收。
     * 由于GC只依赖于引用相等性。这导致在这个情况下，缓存将会通过引用相等(==)而不是对象相等 equals()去进行key之间的比较。
     */
    @Test
    public void testWeakKeys() {
        Cache<String, String> cache = Caffeine.newBuilder()
                .weakKeys()
                .build();
        cache.put(new String("test"), "value1");
        System.out.println(cache.asMap());//{test=value1}
        System.gc();
        System.out.println(cache.asMap()); //value1
    }


    @Test
    public void testWithoutSoftValues() {
        Cache<String, byte[]> cache = Caffeine.newBuilder()
                .build();
        cache.put("test1", new byte[1024 * 1024 * 1024]);
        cache.put("test2", new byte[1024 * 1024 * 1024]);
        cache.put("test3", new byte[1024 * 1024 * 1024]);
        cache.put("test4", new byte[1024 * 1024 * 1024]);
        //Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        System.out.println(cache.asMap());
    }

    @Test
    public void testSoftValues() {
        Cache<String, byte[]> cache = Caffeine.newBuilder()
                .softValues()
                .build();
        cache.put("test1", new byte[1024 * 1024 * 1024]);
        cache.put("test2", new byte[1024 * 1024 * 1024]);
        cache.put("test3", new byte[1024 * 1024 * 1024]);
        cache.put("test4", new byte[1024 * 1024 * 1024]);
        System.out.println(cache.asMap());//{test4=[B@5bf0d49}
    }

}
